# -*- coding: utf-8 -*-

# Copyright (c) 2025 Lijiang
# Licensed under the MIT License
# See LICENSE file for details

"""
基于FFmpeg的视频压缩器，支持真实进度跟踪
"""

import os
import re
import subprocess
import threading
import time
from pathlib import Path
from typing import Optional, Callable, Dict, Any
from PyQt6.QtCore import QThread, pyqtSignal
from .file_handler import FileHandler
from config import get_ffmpeg_config, get_error_message


class FFmpegProgressParser:
    """FFmpeg进度解析器"""
    
    def __init__(self, duration: float):
        self.duration = duration
        self.current_time = 0.0
        self.progress_percentage = 0
        
    def parse_line(self, line: str) -> Optional[int]:
        """
        解析FFmpeg输出行，提取进度信息
        返回进度百分比 (0-100)，如果无法解析则返回None
        """
        # FFmpeg进度输出格式示例：
        # frame=  123 fps= 25 q=28.0 size=    1024kB time=00:00:05.00 bitrate=1677.7kbits/s speed=1.02x
        
        ############################### 匹配时间信息 ###############################
        time_match = re.search(r'time=(\d{2}):(\d{2}):(\d{2})\.(\d{2})', line)
        if time_match:
            hours, minutes, seconds, centiseconds = map(int, time_match.groups())
            current_time = hours * 3600 + minutes * 60 + seconds + centiseconds / 100.0
            
            if self.duration > 0:
                progress = min(100, int((current_time / self.duration) * 100))
                if progress > self.progress_percentage:
                    self.progress_percentage = progress
                    return progress
        
        ############################### 匹配帧数信息（备用方案） ###############################
        frame_match = re.search(r'frame=\s*(\d+)', line)
        if frame_match and hasattr(self, 'total_frames') and self.total_frames > 0:
            current_frame = int(frame_match.group(1))
            progress = min(100, int((current_frame / self.total_frames) * 100))
            if progress > self.progress_percentage:
                self.progress_percentage = progress
                return progress
        
        return None
    
    def set_total_frames(self, total_frames: int):
        """设置总帧数（用于基于帧数的进度计算）"""
        self.total_frames = total_frames


class FFmpegCompressionWorker(QThread):
    """基于FFmpeg的视频压缩工作线程"""
    
    # 信号定义
    progress_updated = pyqtSignal(int)  # 进度更新信号 (0-100)
    status_updated = pyqtSignal(str)    # 状态更新信号
    compression_finished = pyqtSignal(bool, str)  # 压缩完成信号 (成功, 消息)
    file_info_updated = pyqtSignal(dict)  # 文件信息更新信号
    
    def __init__(self, input_path: str, output_path: str, compression_ratio: float, 
                 ffmpeg_path: str = None):
        super().__init__()
        self.input_path = input_path
        self.output_path = output_path
        self.compression_ratio = compression_ratio
        self.ffmpeg_path = ffmpeg_path or self._find_ffmpeg()
        self.is_cancelled = False
        self.process = None
        self.progress_parser = None
        
    def _find_ffmpeg(self) -> str:
        """查找FFmpeg可执行文件路径"""
        ############################### 首先尝试使用项目中的FFmpeg ###############################
        project_ffmpeg = os.path.join(os.path.dirname(__file__), '..', 'ffmpeg', 'bin', 'ffmpeg.exe')
        if os.path.exists(project_ffmpeg):
            return project_ffmpeg
        
        ############################### 尝试系统PATH中的ffmpeg ###############################
        try:
            result = subprocess.run(['ffmpeg', '-version'], 
                                  capture_output=True, text=True, encoding='utf-8', 
                                  errors='replace', timeout=5)
            if result.returncode == 0:
                return 'ffmpeg'
        except:
            pass
        
        raise RuntimeError(get_error_message("ffmpeg_not_found"))
    
    def _get_video_info(self) -> Dict[str, Any]:
        """获取视频信息"""
        try:
            ffprobe_path = self.ffmpeg_path.replace('ffmpeg.exe', 'ffprobe.exe')
            if not os.path.exists(ffprobe_path):
                ffprobe_path = 'ffprobe'
            
            cmd = [
                ffprobe_path,
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_format',
                '-show_streams',
                self.input_path
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True, 
                                  encoding='utf-8', errors='replace', timeout=30)
            if result.returncode != 0:
                raise RuntimeError(f"FFprobe执行失败: {result.stderr}")
            
            import json
            info = json.loads(result.stdout)
            
            # 提取视频流信息
            video_stream = None
            for stream in info.get('streams', []):
                if stream.get('codec_type') == 'video':
                    video_stream = stream
                    break
            
            if not video_stream:
                raise RuntimeError("未找到视频流")
            
            return {
                'duration': float(video_stream.get('duration', 0)),
                'width': int(video_stream.get('width', 0)),
                'height': int(video_stream.get('height', 0)),
                'fps': eval(video_stream.get('r_frame_rate', '0/1')),
                'total_frames': int(video_stream.get('nb_frames', 0)),
                'bitrate': int(video_stream.get('bit_rate', 0))
            }
        except Exception as e:
            raise RuntimeError(f"获取视频信息失败: {str(e)}")
    
    def _build_ffmpeg_command(self, video_info: Dict[str, Any]) -> list:
        """构建FFmpeg命令"""
        ############################### 计算新的尺寸 ###############################
        # 压缩比例：0%表示不压缩(100%原尺寸)，90%表示最大压缩(10%原尺寸)
        # 所以实际缩放比例 = 1 - compression_ratio
        scale_ratio = 1.0 - self.compression_ratio
        new_width = int(video_info['width'] * scale_ratio)
        new_height = int(video_info['height'] * scale_ratio)
        
        ############################### 确保尺寸为偶数 ###############################
        new_width = new_width if new_width % 2 == 0 else new_width - 1
        new_height = new_height if new_height % 2 == 0 else new_height - 1
        
        ############################### 确保路径使用正确的编码 ###############################
        input_path = str(Path(self.input_path).resolve())
        output_path = str(Path(self.output_path).resolve())
        
        ############################### 获取FFmpeg配置 ###############################
        ffmpeg_config = get_ffmpeg_config()
        
        cmd = [
            self.ffmpeg_path,
            '-i', input_path,
            '-vf', f'scale={new_width}:{new_height}',
            '-c:v', ffmpeg_config['video_codec'],
            '-preset', ffmpeg_config['preset'],
            '-crf', ffmpeg_config['crf'],
            '-c:a', ffmpeg_config['audio_codec'],
            '-b:a', ffmpeg_config['audio_bitrate'],
            '-y',  # 覆盖输出文件
            output_path
        ]
        
        return cmd
    
    def _monitor_progress(self):
        """监控FFmpeg进程进度"""
        if not self.process or not self.progress_parser:
            return
        
        try:
            print(f"[FFmpeg日志] 开始监控压缩进度...")
            while self.process.poll() is None and not self.is_cancelled:
                line = self.process.stderr.readline()
                if line:
                    # 现在subprocess直接返回字符串，无需解码
                    line_str = line.strip()
                    
                    # 输出所有FFmpeg日志到控制台
                    if line_str:
                        print(f"[FFmpeg输出] {line_str}")
                    
                    progress = self.progress_parser.parse_line(line_str)
                    if progress is not None:
                        print(f"[FFmpeg进度] 当前进度: {progress}%")
                        self.progress_updated.emit(progress)
                        self.status_updated.emit(f"压缩进度: {progress}%")
                        
                        # 当进度达到100%时，额外输出完成信息
                        if progress >= 100:
                            print(f"[FFmpeg进度] 压缩进度已达到100%")
                            
        except Exception as e:
            print(f"[FFmpeg错误] 进度监控错误: {e}")
    
    def run(self):
        """执行压缩任务"""
        try:
            ############################### 验证输入文件 ###############################
            is_valid, message = FileHandler.validate_video_file(self.input_path)
            if not is_valid:
                self.compression_finished.emit(False, message)
                return
            
            self.status_updated.emit("正在分析视频文件...")
            self.progress_updated.emit(5)
            
            ############################### 获取视频信息 ###############################
            video_info = self._get_video_info()
            
            if self.is_cancelled:
                return
            
            ############################### 初始化进度解析器 ###############################
            self.progress_parser = FFmpegProgressParser(video_info['duration'])
            if video_info['total_frames'] > 0:
                self.progress_parser.set_total_frames(video_info['total_frames'])
            
            self.status_updated.emit("正在准备压缩...")
            self.progress_updated.emit(10)
            
            ############################### 创建输出目录 ###############################
            os.makedirs(os.path.dirname(self.output_path), exist_ok=True)
            
            ############################### 构建FFmpeg命令 ###############################
            cmd = self._build_ffmpeg_command(video_info)
            print(f"[FFmpeg命令] 执行命令: {' '.join(cmd)}")
            
            if self.is_cancelled:
                return
            
            self.status_updated.emit("开始压缩视频...")
            self.progress_updated.emit(15)
            
            ############################### 启动FFmpeg进程 ###############################
            print(f"[FFmpeg启动] 正在启动FFmpeg进程...")
            env = os.environ.copy()
            env['PYTHONIOENCODING'] = 'utf-8'
            
            self.process = subprocess.Popen(
                cmd,
                stderr=subprocess.PIPE,
                stdout=subprocess.PIPE,
                creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0,
                env=env,
                text=True,   # 使用文本模式
                encoding='utf-8',  # 明确指定UTF-8编码
                errors='replace',  # 遇到无法解码的字符时替换为占位符
                bufsize=1    # 行缓冲
            )
            print(f"[FFmpeg启动] FFmpeg进程已启动，PID: {self.process.pid}")
            
            ############################### 启动进度监控线程 ###############################
            progress_thread = threading.Thread(target=self._monitor_progress)
            progress_thread.daemon = True
            progress_thread.start()
            
            ############################### 等待进程完成 ###############################
            print(f"[FFmpeg等待] 等待FFmpeg进程完成...")
            return_code = self.process.wait()
            print(f"[FFmpeg完成] FFmpeg进程已结束，返回码: {return_code}")
            
            if self.is_cancelled:
                print(f"[FFmpeg取消] 压缩任务已被取消")
                return
            
            if return_code == 0:
                print(f"[FFmpeg成功] FFmpeg执行成功，开始验证输出文件...")
                ############################### 验证输出文件 ###############################
                print(f"[FFmpeg验证] 检查输出文件: {self.output_path}")
                if os.path.exists(self.output_path) and os.path.getsize(self.output_path) > 0:
                    original_size = os.path.getsize(self.input_path)
                    output_size = os.path.getsize(self.output_path)
                    compression_ratio_actual = output_size / original_size
                    
                    print(f"[FFmpeg验证] 输出文件验证成功")
                    print(f"[FFmpeg验证] 原始文件大小: {FileHandler.get_file_size(self.input_path)}")
                    print(f"[FFmpeg验证] 压缩后大小: {FileHandler.get_file_size(self.output_path)}")
                    print(f"[FFmpeg验证] 实际压缩比: {compression_ratio_actual:.1%}")
                    
                    self.progress_updated.emit(100)
                    self.status_updated.emit("压缩完成")
                    
                    ############################### 发送文件信息 ###############################
                    file_info = {
                        'original_size': FileHandler.get_file_size(self.input_path),
                        'compressed_size': FileHandler.get_file_size(self.output_path),
                        'compression_ratio': f"{compression_ratio_actual:.1%}",
                        'output_path': self.output_path
                    }
                    self.file_info_updated.emit(file_info)
                    
                    print(f"[FFmpeg完成] 压缩任务成功完成！")
                    self.compression_finished.emit(True, f"压缩成功！输出文件: {self.output_path}")
                else:
                    print(f"[FFmpeg错误] 输出文件验证失败 - 文件不存在或大小为0")
                    self.compression_finished.emit(False, "压缩失败：输出文件无效")
            else:
                print(f"[FFmpeg错误] FFmpeg执行失败，返回码: {return_code}")
                self.compression_finished.emit(False, f"FFmpeg执行失败，返回码: {return_code}")
                
        except Exception as e:
            print(f"[FFmpeg异常] 压缩过程中发生错误: {str(e)}")
            print(f"[FFmpeg异常] 错误类型: {type(e).__name__}")
            import traceback
            print(f"[FFmpeg异常] 错误堆栈: {traceback.format_exc()}")
            self.compression_finished.emit(False, f"压缩过程中发生错误: {str(e)}")
        finally:
            print(f"[FFmpeg清理] 开始清理资源...")
            self.cleanup()
            print(f"[FFmpeg清理] 资源清理完成")
    
    def cancel(self):
        """取消压缩任务"""
        self.is_cancelled = True
        self.status_updated.emit("正在取消压缩...")
        
        if self.process and self.process.poll() is None:
            try:
                self.process.terminate()
                ############################### 等待进程结束，最多等待5秒 ###############################
                try:
                    self.process.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    self.process.kill()
            except:
                pass
        
        self.cleanup()
    
    def cleanup(self):
        """清理资源"""
        if self.process:
            try:
                if self.process.poll() is None:
                    self.process.terminate()
                self.process.stderr.close()
                self.process.stdout.close()
            except:
                pass
            self.process = None


class FFmpegVideoCompressor:
    """基于FFmpeg的视频压缩器主类"""
    
    def __init__(self, ffmpeg_path: str = None):
        self.ffmpeg_path = ffmpeg_path
        self.workers = []  # 存储所有工作线程
    
    def compress_video(self, input_path: str, output_path: str, compression_ratio: float,
                      progress_callback: Optional[Callable] = None,
                      status_callback: Optional[Callable] = None,
                      finished_callback: Optional[Callable] = None,
                      file_info_callback: Optional[Callable] = None) -> FFmpegCompressionWorker:
        """压缩单个视频文件"""
        
        ############################### 创建输出路径 ###############################
        if not output_path:
            output_path = FileHandler.generate_output_path(input_path, compression_ratio)
        
        ############################### 创建压缩工作线程 ###############################
        worker = FFmpegCompressionWorker(input_path, output_path, compression_ratio, self.ffmpeg_path)
        
        ############################### 连接信号 ###############################
        if progress_callback:
            worker.progress_updated.connect(progress_callback)
        if status_callback:
            worker.status_updated.connect(status_callback)
        if finished_callback:
            worker.compression_finished.connect(finished_callback)
        if file_info_callback:
            worker.file_info_updated.connect(file_info_callback)
        
        ############################### 添加到工作线程列表 ###############################
        self.workers.append(worker)
        
        ############################### 启动压缩 ###############################
        worker.start()
        
        return worker
    
    def cancel_all(self):
        """取消所有压缩任务"""
        for worker in self.workers:
            if worker.isRunning():
                worker.cancel()
                worker.wait(5000)  # 等待5秒
    
    def cancel_worker(self, worker: FFmpegCompressionWorker):
        """取消指定的压缩任务"""
        if worker in self.workers:
            worker.cancel()
            worker.wait(5000)
            if worker in self.workers:
                self.workers.remove(worker)
    
    def get_active_workers(self):
        """获取活跃的工作线程"""
        return [worker for worker in self.workers if worker.isRunning()]
    
    def cleanup_finished_workers(self):
        """清理已完成的工作线程"""
        self.workers = [worker for worker in self.workers if worker.isRunning()]
